/*
 * MIT License
 *
 * Copyright (c) 2024-2025 milkpotatoes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

export { };

const READABLE_STREAM_DEFAULT_READER_NOT_ALLOW_LOCKED_STREAM = 'ReadableStreamDefaultReader constructor can only '
    + 'accept readable streams that are not yet locked to a reader';
const CANNOT_READ_FROM_RELEASED_STREAMREADER = 'This readable stream reader has been released and '
    + 'cannot be used to read from its previous owner stream';
const CANNOT_ENQUEUE_TO_CLOSED_STREAM = 'Cannot enqueue a chunk into a readable stream '
    + 'that is closed or has been requested to be closed';
const ILLEGAL_INVOCATION = 'Illegal invocation';

const LOCAL_ERR_ILLEGAL_CONSTRUCTOR: typeof ERR_ILLEGAL_CONSTRUCTOR = Reflect.get(globalThis, 'ERR_ILLEGAL_CONSTRUCTOR');

class CountQueuingStrategy {
    highWaterMark: number;

    constructor(highWaterMark: { highWaterMark: number }) {
        this.highWaterMark = highWaterMark.highWaterMark;
    }
    static size() {
        return 1;
    }
}

interface InnerReadableStreamDefaultControllerData<T> {
    stream: ReadableStream<T>;
    queueSize: number;
    closed: boolean;
    queue: { data: T, size: number }[];
    getSize?: (chunk: any) => number
}
const innterReadableStreamDefaultControllerMaps: WeakMap<ReadableStreamDefaultController<any>,
    InnerReadableStreamDefaultControllerData<any>> = new WeakMap();

const createReadableStreamDefaultController: <T>(stream: ReadableStream<T>, init?: QueuingStrategy) => ReadableStreamDefaultController<T> =
    <T>(stream: ReadableStream<T>, init?: QueuingStrategy) => {
        const controller = Object.create(ReadableStreamDefaultController.prototype) as ReadableStreamDefaultController<T>;
        const data: InnerReadableStreamDefaultControllerData<T> = {
            stream: stream,
            queueSize: init?.highWaterMark ?? 1,
            closed: false,
            queue: [],
            getSize: init?.size ?? undefined,
        };
        innterReadableStreamDefaultControllerMaps.set(controller, data)
        return controller;
    }
class ReadableStreamDefaultController<T> {
    constructor() {
        throw new LOCAL_ERR_ILLEGAL_CONSTRUCTOR('ReadableStreamDefaultController');
    }
    get desiredSize(): number {
        const data = innterReadableStreamDefaultControllerMaps.get(this)!;
        return data.queueSize - data.queue.reduce((sum, next) => sum + next.size, 0);
    }
    close(): void {
        if (!(this instanceof ReadableStreamDefaultController)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innterReadableStreamDefaultControllerMaps.get(this)!;
        data.closed = true;
    }
    enqueue(chunk: T): void {
        if (!(this instanceof ReadableStreamDefaultController)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innterReadableStreamDefaultControllerMaps.get(this)!;
        if (data.closed) {
            throw new TypeError('Failed to execute \'enqueue\' on \'ReadableStreamDefaultController\': '
                + CANNOT_ENQUEUE_TO_CLOSED_STREAM);
        }
        const streamData = innerReadableStreamMaps.get(data.stream)!;
        if (streamData.reader) {
            const readerData = innterReadableStreamDefaultReaderMaps.get(streamData.reader)!;
            if (readerData.readerHandlers.length > 0) {
                readerData.readerHandlers.shift()!.resolve!({ value: chunk, done: false });
                return;
            }
        }

        const getSize = data.getSize;
        const size = getSize ? (getSize(chunk) ?? 1) : 1;
        data.queue.push({ data: chunk, size: size });
    }
    error(e: any): void {
        if (!(this instanceof ReadableStreamDefaultController)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innterReadableStreamDefaultControllerMaps.get(this)!;
        const streamData = innerReadableStreamMaps.get(data.stream)!;
        if (!('error' in streamData)) {
            streamData.error = e;
        }
        if (!streamData.reader) {
            return;
        }
        const readerData = innterReadableStreamDefaultReaderMaps.get(streamData.reader)!;
        readerData.readerHandlers.forEach(({ reject: j }) => {
            j!(e);
        });
    }
}

interface PromiseHandler {
    resolve?: (value: any) => void;
    reject?: (reason: any) => void;
}
interface ReadableStreamDefaultReaderData<T> {
    owner?: ReadableStream<T>;
    closedPromise: Promise<void>;
    closedHandler: PromiseHandler;
    readerHandlers: PromiseHandler[];
}
const innterReadableStreamDefaultReaderMaps: WeakMap<ReadableStreamDefaultReader<any>, ReadableStreamDefaultReaderData<any>> =
    new WeakMap();
class ReadableStreamDefaultReader<T> {
    constructor(stream: ReadableStream<T>) {
        const FAILED_TO_CONSTRUCT = 'Failed to construct \'ReadableStreamDefaultReader\': ';
        if (arguments.length < 1) {
            throw new TypeError(FAILED_TO_CONSTRUCT
                + '1 argument required, but only 0 present.')
        }
        if (!(stream instanceof ReadableStream)) {
            throw new TypeError(FAILED_TO_CONSTRUCT
                + 'parameter 1 is not of type \'ReadableStream\'.')
        }
        const streamData = innerReadableStreamMaps.get(stream)!;
        if (streamData.locked) {
            throw new TypeError(FAILED_TO_CONSTRUCT
                + READABLE_STREAM_DEFAULT_READER_NOT_ALLOW_LOCKED_STREAM);
        }
        let resolve: (value: any) => void;
        let reject: (value: any) => void;
        const promise: Promise<void> = new Promise((r, j) => {
            resolve = r;
            reject = j;
        });
        innterReadableStreamDefaultReaderMaps.set(this, {
            owner: stream,
            closedPromise: promise,
            closedHandler: { resolve: resolve!, reject: reject! },
            readerHandlers: [],
        });
    }
    get closed(): Promise<void> {
        const data = innterReadableStreamDefaultReaderMaps.get(this)!;
        return data.closedPromise;
    }
    cancel(reason: any): Promise<void> {
        if (!(this instanceof ReadableStreamDefaultReader)) {
            return Promise.reject(new TypeError(ILLEGAL_INVOCATION));
        }
        const data = innterReadableStreamDefaultReaderMaps.get(this)!;
        if (!data.owner) {
            return Promise.reject(new TypeError('Failed to execute \'cancel\' on \'ReadableStreamDefaultReader\': '
                + CANNOT_READ_FROM_RELEASED_STREAMREADER));
        }
        const streamData = innerReadableStreamMaps.get(data.owner)!;
        if ('error' in streamData) {
            return Promise.reject(streamData.error);
        }
        for (let handler = data.readerHandlers.shift(); handler; handler = data.readerHandlers.shift()) {
            handler.resolve!({ value: undefined, done: true });
        }
        if (streamData.cancel) {
            streamData.cancel(reason);
        }
        return Promise.resolve();
    }
    read(): Promise<ReadableStreamDefaultReaderDataResult<T>> {
        if (!(this instanceof ReadableStreamDefaultReader)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innterReadableStreamDefaultReaderMaps.get(this)!;
        if (!data.owner) {
            return Promise.reject(new TypeError('Failed to execute \'read\' on \'ReadableStreamDefaultReader\': '
                + CANNOT_READ_FROM_RELEASED_STREAMREADER));
        }
        const streamData = innerReadableStreamMaps.get(data.owner)!;
        if (streamData.error) {
            return Promise.reject(streamData.error);
        }
        const ctrlData = innterReadableStreamDefaultControllerMaps.get(streamData.controller)!;
        const queue = ctrlData.queue;

        if (queue.length > 0) {
            return Promise.resolve({ value: ctrlData.queue.shift()?.data, done: false });
        }
        if (ctrlData.closed) {
            return Promise.resolve({ value: undefined, done: true });
        }
        if (streamData.pull && !streamData.pullFinish) {
            streamData.pullFinish = false;
            new Promise(async () => {
                return await streamData.pull!(streamData.controller);
            }).then(() => {
                streamData.pullFinish = true;
            });
        }
        const handler: PromiseHandler = {};
        const promise: Promise<ReadableStreamDefaultReaderDataResult<T>> = new Promise((r, j) => {
            handler.resolve = r;
            handler.reject = j;
        });
        data.readerHandlers.push(handler);
        return promise;
    }
    releaseLock(): void {
        if (!(this instanceof ReadableStreamDefaultReader)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innterReadableStreamDefaultReaderMaps.get(this)!;
        if (!data.owner) {
            return;
        }
        if (data.readerHandlers) {
            data.readerHandlers.forEach(({ reject: j }) => {
                j!(new TypeError('Releasing Default reader'));
            });
        }
        const streamData = innerReadableStreamMaps.get(data.owner)!;
        streamData.reader = undefined;
        streamData.locked = false;
    }
}

interface ReadableStreamData<T> {
    locked: boolean;
    controller: ReadableStreamDefaultController<T>;
    reader?: ReadableStreamDefaultReader<T>;
    pull?: (controller: ReadableStreamDefaultController<T>) => void;
    pullFinish?: boolean;
    cancel?: (reason: any) => void;
    reason?: any;
    error?: any;
}
const innerReadableStreamMaps: WeakMap<ReadableStream<any>, ReadableStreamData<any>> = new WeakMap();

class ReadableStream<T> {
    constructor(underlyingSource?: UnderlyingSource<T>, queuingStrategy?: QueuingStrategy) {
        if (underlyingSource?.type) {
            throw new TypeError('Failed to construct \'ReadableStream\': Invalid type is specified');
        }
        const controller = createReadableStreamDefaultController(this, queuingStrategy);
        let data: ReadableStreamData<T> = {
            locked: false,
            controller: controller,
        };
        innerReadableStreamMaps.set(this, data);
        if (underlyingSource?.start) {
            underlyingSource.start(controller);
        }
        if (underlyingSource) {
            const { cancel, pull } = underlyingSource;
            data.cancel = cancel;
            data.pull = pull;
        }
    }
    get locked() {
        const data = innerReadableStreamMaps.get(this)!
        return data.locked;
    }
    cancel(reason: any): Promise<void> {
        return new Promise((r, j) => {
            if (!(this instanceof ReadableStream)) {
                j(new TypeError(ILLEGAL_INVOCATION));
                return;
            }
            const data = innerReadableStreamMaps.get(this)!;
            if (data.locked) {
                j(new TypeError('Failed to execute \'cancel\' on \'ReadableStream\': Cannot cancel a locked stream'));
                return;
            }
            data.reason = reason;
            if (data.cancel) {
                data.cancel(reason);
            }
            data.controller.close();
            const ctrlData = innterReadableStreamDefaultControllerMaps.get(data.controller)!;
            ctrlData.queue.slice(0, ctrlData.queue.length);
            r();
            return;
        });
    }
    getReader(mode?: { mode?: 'byob' }): ReadableStreamDefaultReader<T> {
        if (!(this instanceof ReadableStream)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const data = innerReadableStreamMaps.get(this)!;
        if (data.locked) {
            throw new TypeError('Failed to execute \'getReader\' on ReadableStream: '
                + READABLE_STREAM_DEFAULT_READER_NOT_ALLOW_LOCKED_STREAM);
        }
        const reader = new ReadableStreamDefaultReader(this);
        data.locked = true;
        data.reader = reader;
        return reader;
    }
}

interface ReadableStatusData {
    closed?: boolean;
    error?: any;
    paused?: boolean;
    end?: number;
}

type ValidReadableType = string | Uint8Array | null | any

const innerReadableMap: WeakMap<Readable<ValidReadableType>, ReadableStream<ValidReadableType>> = new WeakMap();
const innerReadableStatusMap: WeakMap<Readable<ValidReadableType>, ReadableStatusData> = new WeakMap();
const innerReadableReaderMap: WeakMap<Readable<ValidReadableType>, ReadableStreamDefaultReader<ValidReadableType>> = new WeakMap();
const innerEventListener: WeakMap<EventTarget, { [index: string]: WeakMap<Function, Function> }> = new WeakMap();

const addEventListener = (that: EventTarget, name: string, proxy: EventListener, target: Function) => {
    let listeners = innerEventListener.get(that);
    if (listeners === undefined) {
        listeners = {};
        innerEventListener.set(that, listeners);
    }
    if (listeners[name] == undefined)
        listeners[name] = new WeakMap();
    listeners[name]!.set(target, proxy);
    that.addEventListener(name, proxy);
}

const getRegisteredFunction = (that: EventTarget, name: string, target: Function) => {
    const listeners = innerEventListener.get(that)![name];
    if (listeners === undefined) return undefined;
    return listeners.get(target);
}

const removeEventListener = (that: EventTarget, name: string, target: Function) => {
    const fn = getRegisteredFunction(that, name, target);
    if (fn === undefined) return;
    that.removeEventListener(name, fn as EventListener);
}

function typeCheck(instance: Object, constructor: Object): void {
    if (!(instance instanceof (constructor as any))) {
        throw new TypeError(ILLEGAL_INVOCATION);
    }
}

interface ReadableDataEvent extends Event {
    data: any;
}

class Readable<T> extends EventTarget {
    constructor() {
        throw new LOCAL_ERR_ILLEGAL_CONSTRUCTOR('Readable');
        super();
    }
    on(event: string, callback: Function): void {
        typeCheck(this, Readable);
        if (!(this instanceof Readable)) {
            throw new TypeError(ILLEGAL_INVOCATION);
        }
        const cb = callback.bind(this);
        switch (event) {
            case 'data':
                addEventListener(this, event, (e: Event) => cb((e as ReadableDataEvent).data), callback);
                break;
            case 'error':
                addEventListener(this, event,
                    async (e: Event) => cb(await innerReadableReaderMap.get(this)!.closed), callback);
                break;
            default:
                addEventListener(this, event, (e: Event) => cb(), callback);
                break;
        }
    }
    off(event: string, callback: Function): void {
        typeCheck(this, Readable);
        removeEventListener(this, event, callback);
    }
    destroy(reason: Error): Readable<T> {
        typeCheck(this, Readable);
        const stream = innerReadableMap.get(this)!;
        innerReadableStatusMap.get(this)!.error = reason;
        stream.cancel(reason)
            .then(() => this.dispatchEvent(new Event('error')));
        return this;
    }
    get closed(): boolean {
        typeCheck(this, Readable);
        return innerReadableStatusMap.get(this)!.closed ?? false;
    }
    get destroyed(): boolean {
        typeCheck(this, Readable);
        return innerReadableStatusMap.get(this)!.error !== undefined;
    }
    isPaused(): boolean {
        typeCheck(this, Readable);
        return innerReadableStatusMap.get(this)!.paused ?? false;
    }
    pause(): Readable<T> {
        typeCheck(this, Readable);
        const data = innerReadableStatusMap.get(this)!;
        if (!data.paused) {
            this.dispatchEvent(new Event('pause'));
            data.paused = true;
        }
        return this;
    }
    get readable(): boolean {
        typeCheck(this, Readable);
        const data = innerReadableStatusMap.get(this)!;
        if (data.closed || data.error !== undefined || (data?.end ?? 0) > 0) {
            return false;
        }
        return true;
    }
    get readableAborted(): boolean {
        typeCheck(this, Readable);
        return innerReadableStatusMap.get(this)!.error !== undefined;
    }
    get readableDidRead(): boolean {
        typeCheck(this, Readable);
        const streamData = innerReadableStreamMaps.get(innerReadableMap.get(this)!)!;
        const ctrlData = innterReadableStreamDefaultControllerMaps.get(streamData.controller)!;
        const queue = ctrlData.queue;
        return queue.length === 0;
    }
    get readableEnded(): boolean {
        typeCheck(this, Readable);
        const data = innerReadableStatusMap.get(this)!;
        return (data.end ?? 0) > 1;
    }
    get errored(): boolean {
        typeCheck(this, Readable);
        return innerReadableStatusMap.get(this)!.error !== undefined;
    }
    resume(): Readable<T> {

        typeCheck(this, Readable);
        innerReadableStatusMap.get(this)!.paused = false;
        return this;
    }
}

const dispatchOnReadable = async (readable: Readable<any>, reader: ReadableStreamDefaultReader<any>) => {
    const data = await reader.read()
    {
        const e = new Event('data') as ReadableDataEvent;
        e.data = data.value;
        readable.dispatchEvent(e);
    }
    if (readable.readable) queueMicrotask(() => (dispatchOnReadable(readable, reader)));

    if (readable.isPaused() && !readable.closed) {
        readable.addEventListener('resume', () => {
            dispatchOnReadable(readable, reader);
        }, { once: true });
    }
}

const createReadable: <T>(underlyingSource?: UnderlyingSource<T>) => Readable<T> =
    <T>(underlyingSource?: UnderlyingSource<T>) => {
        const et = new EventTarget();
        const readable = Object.setPrototypeOf(et, Readable.prototype) as Readable<T>;

        const stream = new ReadableStream<T>(underlyingSource);
        const reader = stream.getReader();
        innerReadableMap.set(readable, stream);
        innerReadableStatusMap.set(readable, {});
        innerReadableReaderMap.set(readable, reader);
        innerEventListener.set(readable, {});
        dispatchOnReadable(readable, reader);
        return readable;
    }

registerTempInnerMethod('createReadable', createReadable);

const exports = {
    'CountQueuingStrategy': CountQueuingStrategy,
    'ReadableStreamDefaultController': ReadableStreamDefaultController,
    'ReadableStreamDefaultReader': ReadableStreamDefaultReader,
    'ReadableStream': ReadableStream,
    'Readable': Readable,
} as { [index: string]: any };

for (let k in exports) {
    Object.defineProperty(globalThis, k, {
        value: exports[k]!,
        enumerable: false,
        configurable: true,
        writable: true,
    });
}
